package ml;

import java.util.ArrayList;

public class Trading {
	private double m_asset = 0.0;
	private ArrayList<Ticket> m_tickets = null;
	private int m_decision_time = 4;
	private double m_decision_rate = 0.6;
	private ArrayList<Double> m_decisions = null;
	private double m_init_stop_loss = 0.002;
	private double m_init_take_profit = 0.008;
	private double m_leverage = 1.0;
	private ArrayList<Double> m_assets = new ArrayList<Double>();
	private ArrayList<Double> m_buy_vols = new ArrayList<Double>();
	private ArrayList<Double> m_sell_vols = new ArrayList<Double>();

	private double m_max_vols = 0.0;
	private double m_last_price = -1000.0;

	public Trading(double asset, int decision_time, double decision_rate, double stop_loss, double take_profit, double leverage) {
		m_asset = asset;
		m_tickets = new ArrayList<Ticket>();
		m_decision_time = decision_time;
		m_decision_rate = decision_rate;
		m_decisions = new ArrayList<Double>(m_decision_time);
		for (int i = 0; i < m_decision_time; ++i) {
			m_decisions.add(0.0);
		}
		m_init_stop_loss = stop_loss;
		m_init_take_profit = take_profit;
		m_leverage = leverage;
	}

	public ArrayList<Double> get_assets() {
		return m_assets;
	}

	public ArrayList<Double> get_buy_vols() {
		return m_buy_vols;
	}

	public ArrayList<Double> get_sell_vols() {
		return m_sell_vols;
	}

	public void Update(ArrayList<Double> prices, ArrayList<Boolean> rise_labels, ArrayList<Boolean> drop_labels,
					boolean close_at_end) {
		assert (prices.size() == rise_labels.size());
		assert (prices.size() == drop_labels.size());

		if (prices.size() > 0) {
			if (m_last_price < 0) {
				m_last_price = prices.get(0);
			}
			for (int i = 0; i < prices.size(); ++i) {
				Update(prices.get(i), rise_labels.get(i), drop_labels.get(i));
			}
			if (close_at_end) {
				UpdateAtEnd(prices.get(prices.size() - 1));
			}
		}

		System.out.println("Max Vols: " + m_max_vols);
		System.out.println();
	}

	private void Update(double price, boolean rise_label, boolean drop_label) {
		if (m_asset < Double.MIN_VALUE) {
			return;
		}

		double vols = 0.0;
		for (Ticket aTicket : m_tickets) {
			if (!aTicket.m_is_closed) {
				vols += aTicket.m_volume;
				UpdateAsset(price, aTicket);
				CheckClose(price, aTicket, false);
				CheckMoveStopLoss(price, aTicket);
			}
		}
		m_last_price = price;

		if (vols > m_max_vols) {
			m_max_vols = vols;
		}

		UpdateDecisions(rise_label, drop_label);

		ArrayList<Ticket> new_tickets = new ArrayList<Ticket>();
		for (Ticket aTicket : m_tickets) {
			if (!aTicket.m_is_closed) {
				new_tickets.add(aTicket);
			}
		}
		m_tickets = new_tickets;

		m_assets.add(m_asset);

		CheckBuyOrSell(price);

		UpdateVolumes();
	}

	public void UpdateAtEnd(double price) {
		for (Ticket aTicket : m_tickets) {
			if (!aTicket.m_is_closed) {
				CheckClose(price, aTicket, true);
			}
		}
	}

	private void UpdateAsset(double price, Ticket ticket) {
		double profit = 0.0;
		if (ticket.m_is_buy) {
			profit = (price - m_last_price);
		} else {
			profit = (m_last_price - price);
		}
		if (MLConstants.CHECK_CLOSE_EVERY_TIME) {
			m_asset += profit * ticket.m_volume;
		}
	}

	private void UpdateVolumes() {
		double buy_vols = 0.0;
		double sell_vols = 0.0;
		for (Ticket aTicket : m_tickets) {
			if (!aTicket.m_is_closed) {
				if (aTicket.m_is_buy) {
					buy_vols += aTicket.m_volume;
				} else {
					sell_vols += aTicket.m_volume;
				}
			}
		}
		m_buy_vols.add(buy_vols);
		m_sell_vols.add(sell_vols);
	}

	private void UpdateDecisions(boolean rise_label, boolean drop_label) {
		if (rise_label && (!drop_label)) {
			m_decisions.add(1.0);
		} else if (drop_label && (!rise_label)) {
			m_decisions.add(-1.0);
		}

		while (m_decisions.size() > m_decision_time) {
			m_decisions.remove(0);
		}
	}

	private void CheckClose(double price, Ticket ticket, boolean close_at_end) {
		if (ticket.m_is_buy) {
			if ((price >= ticket.m_take_profit) || (price <= ticket.m_stop_loss) || close_at_end) {
				CloseTicket(price, ticket);
			}
		} else {
			if ((price <= ticket.m_take_profit) || (price >= ticket.m_stop_loss) || close_at_end) {
				CloseTicket(price, ticket);
			}
		}
	}

	private void CloseTicket(double price, Ticket ticket) {
		assert (!ticket.m_is_closed);
		double profit = 0.0;
		if (ticket.m_is_buy) {
			profit = (price - ticket.m_open_price);
		} else {
			profit = (ticket.m_open_price - price);
		}

		if (!MLConstants.CHECK_CLOSE_EVERY_TIME) {
			m_asset += profit * ticket.m_volume;
		}

		ticket.m_is_closed = true;
	}

	private void CheckMoveStopLoss(double price, Ticket ticket) {
		if (ticket.m_is_closed) {
			return;
		}
		// TODO: change the fixed moving stop loss to variable stop loss. Need
		// to analyze this algorithm.

//		double a = 4 * m_init_stop_loss * m_init_take_profit * m_init_take_profit;
//		double b = ticket.m_open_price + 0.5 * m_init_take_profit;
//		double c = m_init_take_profit / 6.0;
//		double stop_loss = a * Math.pow(price - b, 2) + c;

//		 double stop_loss = m_init_stop_loss;
//		 double cur_profit = ticket.Profit(price);
//		 if ((cur_profit > 0.0) && (cur_profit < m_init_take_profit)) {
//		 double a = -1.0 / m_init_take_profit;
//		 double c = m_init_stop_loss - 0.25 * a * Math.pow(m_init_take_profit,
//		 2);
//		 stop_loss = a * Math.pow(cur_profit - 0.5 * m_init_take_profit, 2) +
//		 c;
//		 }

		double stop_loss = m_init_stop_loss;

		if (ticket.m_is_buy) {
			if (price >= ticket.m_stop_loss) {
				ticket.m_stop_loss = price - stop_loss;
			}
		} else {
			if (price <= ticket.m_stop_loss) {
				ticket.m_stop_loss = price + stop_loss;
			}
		}
	}

	/*
	 * Return minus if should not trade.
	 */
	private double CalVols(double price, boolean is_buy) {
		double has_vols = 0.0;
		for (Ticket aTicket : m_tickets) {
			if (!aTicket.m_is_closed) {
				if (aTicket.m_is_buy == is_buy) {
					if (aTicket.Profit(price) < 0.0) {
						return -1.0;
					}
					has_vols += aTicket.m_volume;
				} else {
					has_vols -= aTicket.m_volume;
				}
			}
		}

		double max_vols = Math.min(MLConstants.MAX_VOLS, m_asset * MLConstants.MAX_LEVERAGE / (price + MLConstants.SPREAD));
		double vols = MLConstants.FIXED_VOLS;
		if (vols < 0.0) {
			vols = m_asset * m_leverage / (price + MLConstants.SPREAD);
		}

		vols = Math.min(max_vols - has_vols, vols);
		return vols;
	}

	private void CheckBuyOrSell(double price) {
		if (m_decisions.size() <= 0) {
			return;
		} else if (m_decisions.get(m_decisions.size() - 1) > 0.5) {
			for (Ticket aTicket : m_tickets) {
				if ((!aTicket.m_is_closed) && (!aTicket.m_is_buy)) {
					CloseTicket(price, aTicket);
				}
			}

			int votes = 0;
			int votes_threshold = (int) Math.ceil(m_decision_rate * m_decision_time);
			for (double label : m_decisions) {
				if (label > 0.5) {
					++votes;
					if (votes >= votes_threshold) {
						double vols = CalVols(price, true);
						if (vols < 0) {
							return;
						}
						Ticket newTicket = new Ticket(true, vols, price + MLConstants.SPREAD, price + MLConstants.SPREAD
										- m_init_stop_loss, price + MLConstants.SPREAD + m_init_take_profit);
						m_tickets.add(newTicket);
						m_decisions.clear();
						return;
					}
				}
			}
		} else if (m_decisions.get(m_decisions.size() - 1) < -0.5) {
			for (Ticket aTicket : m_tickets) {
				if ((!aTicket.m_is_closed) && (aTicket.m_is_buy)) {
					CloseTicket(price, aTicket);
				}
			}

			int votes = 0;
			int votes_threshold = (int) Math.ceil(m_decision_rate * m_decision_time);
			for (double label : m_decisions) {
				if (label < -0.5) {
					++votes;
					if (votes >= votes_threshold) {
						double vols = CalVols(price, false);
						if (vols < 0) {
							return;
						}
						Ticket newTicket = new Ticket(false, vols, price - MLConstants.SPREAD, price - MLConstants.SPREAD
										+ m_init_stop_loss, price - MLConstants.SPREAD - m_init_take_profit);
						m_tickets.add(newTicket);
						m_decisions.clear();
						return;
					}
				}
			}
		}
	}
}
